package abstractions;

import runners.*;
import runners.Runner.Gender;
import accounts.*;
import lists.*;
import tester.*;
import tester.cobertura.Instrument;

/**
 * Abstractions: Solutions to given questions.
 * @author Adam
 */
@Instrument
public class Abstractions {

	/**
	 * The default constructor.
	 */
	public Abstractions() {}

	/**
	 * Create a static instance of Abstractions:
	 */
	static Abstractions a = new Abstractions();

	// Even though I can write these predicates in line 
	// as I pass them into the filter these predefined ones make life easier.
	
	// Constants used in Runner predicates:
	private static final int maxAge = 40; // Max age desired.
	private static final int time = 180; // Faster than time...
	
	// Runner predicates:
	/**
	 * underMaxAge is an anonymous Class overriding Predicate, 
	 * and overriding the p method.
	 */
	private static UnaryFunction<Boolean, Runner> underMaxAge = 
		new UnaryFunction<Boolean, Runner>() {
		public Boolean f(Runner r) {
			return r.getAge() <= maxAge;}};
			
	/**
	 * fasterThan is an anonymous Class overriding Predicate, 
	 * and overriding the p method.
	 */
	private static UnaryFunction<Boolean, Runner> fasterThan = 
		new UnaryFunction<Boolean, Runner>() {
		public Boolean f(Runner r) {
			return r.totalTime() <= time;}};
			
	/**
	 * male is an anonymous Class overriding Predicate, 
	 * and overriding the p method.
	 */
	private static UnaryFunction<Boolean, Runner> male = 
		new UnaryFunction<Boolean, Runner>() {
		public Boolean f(Runner r) {
			return r.getGender() == Gender.MALE;}};
			
	/**
	 * female is an anonymous Class overriding Predicate, 
	 * and overriding the p method.
	 */
	private static UnaryFunction<Boolean, Runner> female = 
		new UnaryFunction<Boolean, Runner>() {
		public Boolean f(Runner r) {
			return r.getGender() == Gender.FEMALE;}};
	
	// Constants used in Runner predicates:
	private static final String type = "class accounts.Savings";
	private static final Integer lowBalance = 200;
	private static final String owner = "Bob";
	
	// Predicates for Accounts
	/**
	 * typeIs is an anonymous Class overriding Predicate, 
	 * and overriding the p method.
	 */
	private static UnaryFunction<Boolean, Account> typeIs = 
		new UnaryFunction<Boolean, Account>() {
		public Boolean f(Account a) {
			return a.getClassType().equals(type);}};
			
	/**
	 * balanceUnder is an anonymous Class overriding Predicate, 
	 * and overriding the p method.
	 */
	private static UnaryFunction<Boolean, Account> balanceUnder = 
		new UnaryFunction<Boolean, Account>() {
		public Boolean f(Account a) {
			return a.getBalance() <= lowBalance;}};
			
	/**
	 * ownedBy is an anonymous Class overriding Predicate, 
	 * and overriding the p method.
	 */
	private static UnaryFunction<Boolean, Account> ownedBy = 
		new UnaryFunction<Boolean, Account>() {
		public Boolean f(Account a) {
			return a.getOwner().equals(owner);}};
	
	// Examples:
	// Initialize the checking accounts.
	private Account check1 = 
		new Checking(0001, "First Checking Account", 0, "Joe", 0);
	private Account check2 = 
		new Checking(0002, "Second Checking Account", 100, "Bob", 50);
	private Account check3 = 
		new Checking(0003, "Third Checking Account", 500, "Jim", 0);
	// Initialize the savings accounts.
	private Account savings1 = 
		new Savings(0004, "First Savings Account", 0, "Jim", 4.9);
	private Account savings2 = 
		new Savings(0005, "Second Savings Account", 100, "Bob", 8.9);
	private Account savings3 = 
		new Savings(0006, "Third Savings Account", 0, "Joe", 1.1);
	// Initialize the credit accounts.
	private Account credit1 = 
		new Credit(0004, "First Credit Account", 1000, "Adam", 1000, 4.9);
	private Account credit2 = 
		new Credit(0005, "Second Credit Account", 100, "Joe", 200, 8.9);
	private Account credit3 = 
		new Credit(0006, "Third Credit Account", 0, "Jim", 1000, 1.1);
	// Initialize the premium checking accounts.
	private Account pcheck1 = 
		new PremiumChecking(0001, "First Checking Account", 0, "Bob", 0, 0);
	private Account pcheck2 = 
		new PremiumChecking(0002, "Second Checking Account", 100, "Tim", 50, 5);
	private Account pcheck3 = 
		new PremiumChecking(0003, "Third Checking Account", 500, "Viera", 0, 25);

	// Initialize the Runners
	private Runner r1 = new Runner("bob", Gender.MALE, 42, 314, 0, 628);
	private Runner r2 = new Runner("jim", Gender.MALE, 33, 333, 0, 444);
	private Runner r3 = new Runner("joe", Gender.MALE, 16, 66, 0, 160);
	private Runner r4 = new Runner("adam", Gender.MALE, 18, 314, 18, 180);
	private Runner r5 = new Runner("viera", Gender.FEMALE, 50, 333, 0, 200);
	private Runner r6 = new Runner("olin", Gender.MALE, 30, 66, 0, 166);
	private Runner r7 = new Runner("jill", Gender.FEMALE, 42, 314, 18, 120);
	private Runner r8 = new Runner("jane", Gender.FEMALE, 50, 333, 0, 100);
	private Runner r9 = new Runner("joan", Gender.FEMALE, 30, 66, 80, 266);

	// Lists of Runners:
	private List<Runner> mtlor = new MTList<Runner>();
	private List<Runner> lor4 = new ConsList<Runner>(r1,
			new ConsList<Runner>(r2, 
				new ConsList<Runner>(r3, 
					new ConsList<Runner>(r4, 
						new ConsList<Runner>(r5, 
							new ConsList<Runner>(r6, 
								new ConsList<Runner>(r7, 
									new ConsList<Runner>(r8, 
										new ConsList<Runner>(r9, mtlor)))))))));
	private List<Runner> lor5 = new ConsList<Runner>(r8,
			new ConsList<Runner>(r7, 
				new ConsList<Runner>(r3, 
					new ConsList<Runner>(r4, 
						new ConsList<Runner>(r6, 
							new ConsList<Runner>(r9, 
								new ConsList<Runner>(r5, 
									new ConsList<Runner>(r2, 
										new ConsList<Runner>(r1, mtlor)))))))));

	// Lists of Accounts:
	private List<Account> mtloa = new MTList<Account>();
	private List<Account> loa3 = new ConsList<Account>(pcheck1, 
			new ConsList<Account>(pcheck3, 
				new ConsList<Account>(pcheck1, 
					new ConsList<Account>(savings1, 
						new ConsList<Account>(credit1, 
							new ConsList<Account>(check3, 
								new ConsList<Account>(pcheck2, 
									new ConsList<Account>(savings2, 
										new ConsList<Account>(credit3, 
											new ConsList<Account>(check2, 
												new ConsList<Account>(check1, mtloa)))))))))));
	private List<Account> loa4 = new ConsList<Account>(pcheck1, 
			new ConsList<Account>(check2, 
					new ConsList<Account>(savings2, mtloa)));

	/**
	 * Main method
	 * @param args
	 */
	public static void main(String[] args) {
		runnerFunctions();
		accountFunctions();
	}

	/**
	 * This method prints the abstractions.pdf assignment to the console.
	 * Runner Functions:
	 */
	private static void runnerFunctions() {
		/*
		 * Part One:
		 * A. list of all male runners under 40 years old
		 * B. count all male runners under 40 years old
		 * C. Is there a male runner under 40 years old in the list?
		 * Part Two:
		 * A. list of all female runners who finished in under 180 minutes
		 * B. count all female runners who finished in under 180 minutes
		 * C. Is there a female runner who finished in under 180 minutes?
		 * Part Three:
		 * A. list of all runners over 40 who finished in under 180 minutes
		 * B. count all runners over 40 who finished in under 180 minutes
		 * C. Is there a runner over 40 who finished in under 180 minutes?
		 */
		System.out.println("List 4: \n" + a.lor4.toString());
		// Part One:
		// To prove the point of abstracting.... :)
		String s = "male runner(s) under 40 years old in List 4. : ";
		// So this is fully abstracted I was able to filter a list 
		// using two predicates.
		System.out.println("A. list of all " + s + "\n" +
				a.lor4.filter(male).filter(underMaxAge));
		// But it isn't efficient I would rather write my condition inline 
		// so I have more control of what it does, 
		// and it only recurses through the list once:
		System.out.println("B. count all " + s + "\n" +
				a.lor4.count(new UnaryFunction<Boolean, Runner>() {
					public Boolean f(Runner r) {
						return r.getAge() <= maxAge && 
						r.getGender() == Gender.MALE;
					}}) + "\n");
		// Contains could be adapted to become contains object 
		// that fits predicate, but ormap is fine.
		System.out.println("C. Is there a " + s + "\n" +
				a.lor4.ormap(new UnaryFunction<Boolean, Runner>() {
					public Boolean f(Runner r) {
						return r.getAge() <= maxAge && 
						r.getGender() == Gender.MALE;
					}}));
		// Part Two:
		System.out.println("\nA. list of all female runners who " +
				"finished in under 180 minutes in List 4. : \n" + 
				a.lor4.filter(new UnaryFunction<Boolean, Runner>() {
					public Boolean f(Runner r) {
						return r.totalTime() <= time && 
						r.getGender() == Gender.FEMALE;
					}}));
		System.out.println("B. count all female runners who " +
				"finished in under 180 minutes in List 4. : \n" + 
				a.lor4.count(new UnaryFunction<Boolean, Runner>() {
					public Boolean f(Runner r) {
						return r.totalTime() <= time && 
						r.getGender() == Gender.FEMALE;
					}}) + "\n");
		System.out.println("C. Is there a male runner " +
				"under 40 years old in List 4? : \n" + 
				a.lor4.ormap(new UnaryFunction<Boolean, Runner>() {
					public Boolean f(Runner r) {
						return r.totalTime() <= time && 
						r.getGender() == Gender.FEMALE;
					}}));
		// Part Three:
		System.out.println("\nA. list of all runners over 40 who finished " +
				"in under 180 minutes in List 4. : \n" + 
				a.lor4.filter(new UnaryFunction<Boolean, Runner>() {
					public Boolean f(Runner r) {
						return r.totalTime() <= time && r.getAge() <= maxAge;
					}}));
		System.out.println("B. count all runners over 40 who finished " +
				"in under 180 minutes in List 4. : \n" + 
				a.lor4.count(new UnaryFunction<Boolean, Runner>() {
					public Boolean f(Runner r) {
						return r.totalTime() <= time && r.getAge() <= maxAge;
					}}) + "\n");
		System.out.println("C. Is there a runner over 40 who finished " +
				"in under 180 minutes in List 4? : \n" + 
				a.lor4.ormap(new UnaryFunction<Boolean, Runner>() {
					public Boolean f(Runner r) {
						return r.totalTime() <= time && r.getAge() <= maxAge;
					}}));
	}

	/**
	 * This method prints the abstractions.pdf assignment to the console.
	 * Runner Functions:
	 */
	private static void accountFunctions() {
		/*
		 * Part One:
		 * A. list of all accounts with the balance under $200
		 * B. count all accounts with the balance under $200
		 * C. Is there an account with the balance under $200 in this list?
		 * Part Two:
		 * A. list of all accounts with the given customer name
		 * B. count all accounts with the given customer name
		 * C. Is there an account with the given customer name in this list?
		 * Part Three:
		 * A. list of all savings accounts
		 * B. count all savings accounts
		 * C. Is there a savings account in this list?
		 */
		System.out.println("\nList 3: \n" + a.loa3.toString());
		// Part One:
		// Since there is only one condition to be met, 
		// it is more helpful to abstract to a function object here.
		System.out.println("\nA. list of all accounts with the balance " +
				"under $200 in List 3. : \n" + 
				a.loa3.filter(balanceUnder));
		System.out.println("B. count all accounts with the balance " +
				"under $200 in List 3. : \n" + 
				a.loa3.count(balanceUnder) + "\n");
		System.out.println("C. Is there an account with the balance " +
				"under $200 in List 3? : \n" + 
				a.loa3.ormap(balanceUnder));
		// Part Two:
		System.out.println("\nA. list of all accounts with the given " +
				"customer name in List 3, (Bob). : \n" + 
				a.loa3.filter(ownedBy));
		System.out.println("B. count all accounts with the given " +
				"customer name in List 3, (Bob). : \n" + 
				a.loa3.count(ownedBy) + "\n");
		System.out.println("C. Is there an account with the given " +
				"customer name in List 3, (Bob)? : \n" + 
				a.loa3.ormap(ownedBy));
		// Part Three:
		System.out.println("\nA. list of all savings accounts in List 3. : \n" + 
				a.loa3.filter(typeIs));
		System.out.println("B. count all savings accounts in List 3. : \n" + 
				a.loa3.count(typeIs) + "\n");
		System.out.println("C. Is there a savings account in List 3? : \n" + 
				a.loa3.ormap(typeIs));
	}
}